/*
 *  Copyright (C) 2025 ameise <ameise.wang@gmail.com> - All Rights Reserved
 *
 *  This file is part of e3net.
 *
 *  e3net is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  e3net is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with e3net. If not, see <https://www.gnu.org/licenses/>.
 */

package e3net

import (
	"errors"
	"net"
	"sync"

	"gitee.com/ameise84/e3net/internal"
	"gitee.com/ameise84/e3net/internal/common"
	"gitee.com/ameise84/e3pool/go_pool"
)

func newStdTcpAgent(hand TcpAgentHandler, opts *TcpAgentOptions) (*tcpAgent, error) {
	if hand == nil {
		return nil, SocketErrorNoHandler
	}
	s := &tcpAgent{
		hand:     hand,
		opts:     opts,
		connPool: newStdTcpConnPool(opts.connOpts),
	}
	s.goRunner = go_pool.NewGoRunner(s, "tcp agent", go_pool.DefaultOptions().SetSimCount(0).SetBlock(false))
	return s, nil
}

type tcpAgent struct {
	common.Service
	hand     TcpAgentHandler
	opts     *TcpAgentOptions
	connPool *stdTcpConnPool
	connMap  sync.Map
	goRunner go_pool.GoRunner
}

func (ts *tcpAgent) E3LogMarshall() string {
	return "std tcp agent"
}

func (ts *tcpAgent) OnPanic(err error) {
	_gLogger.Error("on panic").Object(ts).Err(err).Println()
}

func (ts *tcpAgent) Start() error {
	return ts.Service.Start(nil)
}

func (ts *tcpAgent) Stop() {
	ts.Service.Stop(func() {
		ts.DisConnectAll()
	})
}

func (ts *tcpAgent) ConnectSync(tag Tag, ctx ConnContext, addr string) (Conn, error) {
	if !ts.IsRunning() {
		return nil, errors.New("tcp agent is not running")
	}
	if ctx == nil {
		return nil, errors.New("stdTcpConn context is nil")
	}
	c, err := ts.goRunner.SyncRun(dialSync, ts, tag, ctx, addr)
	if err != nil {
		return nil, err
	}
	return c.(Conn), err
}

func (ts *tcpAgent) ConnectAsync(tag Tag, ctx ConnContext, addr string) error {
	if !ts.IsRunning() {
		return errors.New("tcp agent is not running")
	}
	if ctx == nil {
		return errors.New("stdTcpConn context is nil")
	}
	return ts.goRunner.AsyncRun(dialAsync, ts, tag, ctx, addr)
}

func (ts *tcpAgent) DisConnectAll() {
	mp := make(map[uint32]*stdTcpConn)
	ts.connMap.Range(func(key, value any) bool {
		c := value.(*stdTcpConn)
		mp[c.instId] = c
		return true
	})
	for _, conn := range mp {
		_ = conn.closeAndWaitRecv()
	}
}

func (ts *tcpAgent) handleCloseConn(c *stdTcpConn) {
	ts.hand.OnAgentClosedConn(ts, c.tag, c.ctx)
	ts.connMap.Delete(c.instId)
	c.ctx = nil
	c.inActive()
	ts.connPool.free(c)
}

func (ts *tcpAgent) handleNewConnection(tag Tag, fd net.Conn, ctx ConnContext) *stdTcpConn {
	c := ts.connPool.take()
	c.active(ts, tag, fd, 0, ts.opts.writeTimeout)
	c.ctx = ctx
	if c.run() {
		ts.hand.OnAgentConnect(ts, ctx, c)
		ts.connMap.Store(c.instId, c)
		return c
	} else {
		ts.hand.OnAgentConnectFailed(ts, tag, ctx, errors.New("agent connect run failed"))
		return nil
	}
}

func (ts *tcpAgent) connectTo(addr string) (net.Conn, error) {
	network := string(internal.TCP)
	fd, err := net.Dial(network, addr)

	if err != nil {
		return nil, err
	}
	return fd, nil
}

func dialAsync(args ...any) {
	_, _ = dialSync(args...)
}

func dialSync(args ...any) (any, error) {
	s := args[0].(*tcpAgent)
	tag := args[1].(Tag)
	ctx := args[2].(ConnContext)
	addr := args[3].(string)
	fd, err := s.connectTo(addr)
	if err != nil {
		s.hand.OnAgentConnectFailed(s, tag, ctx, err)
		return nil, err
	}

	c := s.handleNewConnection(tag, fd, ctx)
	return c, nil
}
